#pragma once
#include <Windows.h>
#include <string>
#include <GdiPlus.h>
#include "h2base.h"


// ============================================================================
// STRUCTURE text_range: range of text segment in container
// ============================================================================
#define TEXT_BEGIN      0
#define TEXT_END        0x7FFFFFFF
typedef struct text_range
{
    int begin;                      // range length = end - begin
    int end;                        // the end point is not included in the range
                                    // e.g. {begin=10, end=14} includes 4 chars as {10, 11, 12, 13}
}TEXT_RANGE;


// ============================================================================
// STRUCTURE text_scope: a number of text ranges
// ============================================================================
typedef struct text_scope
{
    gap_vector<text_range>  ranges;
}TEXT_SCOPE;


// ============================================================================
// STRUCTURE text_char: smallest etom of text container
// ============================================================================
typedef struct text_char
{
    wchar_t             unicode;    // unicode value of the char
    unsigned char       width;      // width of the char
    unsigned char       height;     // height of the char
    unsigned char       style;      // style of the char
}TEXT_CHAR;


// ============================================================================
// STRUCTURE text_line: line of text in the container, consist of a serials of text_char
// ============================================================================
typedef struct text_line
{
    int                 x;          // x positon on canvas
    int                 y;          // y positon on canvas
    int                 width;      // total width of all characters in line
    int                 height;     // total height of all characters in line
    text_range          range;      // range of the line characters
    bool                dirty;      // dirty flag, indicating need redraw or not
}TEXT_LINE;


// ============================================================================
// STRUCTURE text_style: style of text to be displayed on canvas
// ============================================================================
typedef struct text_style
{
    COLORREF            cltext;         // text color
    COLORREF            clback;         // background color
    bool                transparent;    // background transparent flag 
    int                 width;          // font average width
    int                 height;         // font height
    int                 weight;         // font weight
    BYTE                charset;             
    BYTE                italic;     
    BYTE                underline;
    BYTE                strikeout;
    TCHAR               fontfamily[LF_FACESIZE];
    HFONT               font;           // HFONT object of the style
}TEXT_STYLE;




// ============================================================================
// CLASS text_dc: memory DC with the text buffer which accelerates the 
// continuous characters drawing.
// ============================================================================
class text_dc
{
public:
    text_dc() : _hdc(NULL), _hbmp(NULL), _old_hbmp(NULL), _hfont(NULL), _text_buffer_len(0), _bgbrush(NULL) {}
    ~text_dc() { release(); if(_bgbrush)DeleteObject(_bgbrush); }

    bool        init(HWND hwnd);                // initialize canvas
    void        release();                      // release canvas
    bool        resize(int width, int height);  // resize canvas
    void        set_bg_color(COLORREF clback);  // set background color
    void        erase(const RECT* prc = NULL);  // erase canvas
    void        draw_text(int x, int y, COLORREF cltext, COLORREF clback, bool tranparent, HFONT hfont, const text_char& chr);
    void        flash();                        // flash the text buffer, draw text immediatelly
    bool        measure_font(HFONT hfont, wchar_t wchar, unsigned char* pWidth, unsigned char* pHeight);

    inline int  width()  { return _size.cx; }   // get width of canvas
    inline int  height() { return _size.cy; }   // get height of canvas
    inline HDC  handle() { return _hdc; }       // get dc handle

private:
    HWND        _hwnd;                      // parent window handle
    HDC         _hdc;                       // dc handle
    HBITMAP     _hbmp, _old_hbmp;           // bmp object handle
    HBRUSH      _bgbrush;                   // background brush

    SIZE        _size;                      // canvas size
    SIZE        _bmp_size;                  // bmp buffer size, larger than canvas size

    HFONT       _hfont;                     // current font
    COLORREF    _color_front, _color_back;  // current text color
    bool        _bg_transparent;            // bacnground transparent mode

    wchar_t     _text_buffer[1024];         // output text buffer
    int         _text_buffer_len;           // current text buffer length
    RECT        _text_buffer_pos;           // drawing position of text
};



// ============================================================================
// CLASS text_container: main functions are as follows
//  [Text Modification]: insert_text, remove_text, clear_text
//  [Text Retrieval]: get_text, get_line
//  [Selection and Mark]: select, selection_range
//  [Position Conversion]: pos_from_point, word_range_from_pos, line_from_pos
//  [Caret]: move_caret, set_caret_mode(overtype), show_caret
//  [Styling]: add_style, set_style_xxxx, update_style, set_xxxx_color
//  [Canvas]: init_canvas, redraw_canvas, resize_canvas, output_canvas, set_margin
//  [Scrolling]: get_max_v_scroll, get_max_h_scroll, scroll_view
// ============================================================================
class text_container
{
public:
    text_container();
    ~text_container();

    void    debug_info();           // for debug purpose

public:
    // Text Modification
    void        insert_text(int pos, const char* pszText, int style = 0);
    void        insert_text(int pos, const wchar_t* pwszText, int style = 0);
    void        insert_text(int pos, int length, text_char* pccText, int style = 0);
    void        remove_text(const text_range& range);
    void        remove_sel_text();
    void        clear_text();


    // Text Retrieval
    wchar_t         char_at(int pos){ return _text_buffer[pos].unicode; }
    STRING::unicode get_text(const text_range& range);
    STRING::unicode get_line(int line);
    STRING::unicode get_sel_text()  { return get_scope_text(_select_scope); }
    inline int      text_length()   { return _text_buffer.length(); }
    inline int      line_count()    { return _line_buffer.length(); }


    // Selection and Mark
    int         mark_text(const RECT& rcmark, bool single_range = true, bool full_line = true);
    inline void clear_mark()                { dirty_scope(_mark_scope); _mark_scope.ranges.clear(); }
    inline bool is_marked(int pos)          { return in_scope(pos, _mark_scope); }
    inline int  mark_count()                { return _mark_scope.ranges.length(); }
    inline text_range&  mark_at(int idx)    { return _mark_scope.ranges[idx]; }

    void        select_text(const text_range& range, bool clear_first = true);
    inline void select_mark()               { for(int i = 0; i < _mark_scope.ranges.length(); ++i) select_text(_mark_scope.ranges[i], false); }
    text_range& selection_at(int n)         { return _select_scope.ranges[n]; }
    inline int  selection_count()           { return _select_scope.ranges.length(); }
    inline void clear_selection()           { dirty_scope(_select_scope); clear_scope(_select_scope); }
    inline bool is_selected(int pos)        { return in_scope(pos, _select_scope); }


    // Position Conversion
    int         pos_from_point(int x, int y);
    bool        word_range_from_pos(int pos, text_range* pRange);
    int         line_from_pos(int pos);
    text_range& line_range(int line) { return _line_buffer[line].range; }


    // Caret
    void        move_caret(int pos);
    void        set_caret_mode(bool overtype = true);
    void        show_caret(bool showed = true);
    inline int  caret_pos() { return _caret_pos; }


    // Styling
    int         add_style(COLORREF cltext, const wchar_t* fontfamily, int size);
    void        set_style_background(int style, COLORREF clback, bool transparent = false) { _text_styles[style].clback = clback; _text_styles[style].transparent = transparent; }
    void        set_style_weight(int style, int weight)         { _text_styles[style].weight = weight; }
    void        set_style_italic(int style, bool italic)        { _text_styles[style].italic = italic; }
    void        set_style_underline(int style, bool underline)  { _text_styles[style].underline = underline; }
    void        set_style_strikeout(int style, bool strikeout)  { _text_styles[style].strikeout = strikeout; }
    void        update_style(int style);
    void        set_selection_color(COLORREF cltext, COLORREF clback) { _sel_text_color = cltext; _sel_bg_color = clback; }
    void        set_mark_color(COLORREF cltext, COLORREF clback){ _mark_text_color = cltext; _mark_bg_color = clback; }
    void        set_caret_color(COLORREF cltext, COLORREF clback) { _caret_text_color = cltext; _caret_bg_color = clback; if(_caret_pen) DeleteObject(_caret_pen); _caret_pen = CreatePen(PS_SOLID, 2, cltext); }
    void        set_background_color(COLORREF clBackground)     { _canvas.set_bg_color(clBackground); }


    // Canvas
    bool        init_canvas(HWND hWnd);
    void        redraw_canvas(bool dirty_only = false);
    void        output_canvas(HDC hdc, int x, int y, int width, int height);
    void        set_max_canvas_width(int width);
    inline bool resize_canvas(int width, int height)    { return _canvas.resize(width, height); }
    inline void set_margin(int left, int top, int right, int bottom) { _margin.left = left; _margin.top = top; _margin.right = right; _margin.bottom = bottom; }


    // Scrolling
    int         get_max_v_scroll();
    int         get_max_h_scroll();
    void        scroll_view(int column, int row);
    inline int  get_v_scroll()      { return _scroll_pos.y; }
    inline int  get_h_scroll()      { return _scroll_pos.x; }


protected:
    // Event
    void        on_modified(int pos, int modified_length);


protected:
    // canvas related private functions
    inline int  canvas_to_view_x(int x) { return x - _view_pos.x + _margin.left; }
    inline int  canvas_to_view_y(int y) { return y - _view_pos.y + _margin.top; }
    inline int  view_to_canvas_x(int x) { return x + _view_pos.x - _margin.left; }
    inline int  view_to_canvas_y(int y) { return y + _view_pos.y - _margin.top; }
    bool        measure_line(const text_line& tline,  int* piWidth, int* piHeight);

    // range and scope related private functions
    inline bool overlapped_range(text_range& r1, text_range r2) { return !(r1.end <= r2.begin || r1.begin >= r2.end); }
    inline bool mergeable_range(text_range& r1, text_range r2) { return !(r1.end < r2.begin || r1.begin > r2.end); }
    inline bool in_range(int pos, text_range& range) { return (range.begin <= pos && pos < range.end); }
    inline bool in_scope(int pos, text_scope& scope) { for(int i = 0; i < scope.ranges.length(); ++i) if(in_range(pos, scope.ranges[i])) return true; return false; }
    inline void clear_scope(text_scope& scope) { scope.ranges.clear(); }
    void        dirty_scope(text_scope& scope);
    STRING::unicode get_scope_text(text_scope& scope);


protected:
    gap_vector<text_char>       _text_buffer;       // text buffer
    gap_vector<text_line>       _line_buffer;       // line buffer
    gap_vector<text_style>      _text_styles;       // all text styles

    text_scope                  _select_scope;      // selection text ranges
    text_scope                  _mark_scope;        // marked text ranges

    int                         _caret_pos;         // caret position
    bool                        _caret_show;        // caret showing flag
    bool                        _caret_overtype;    // caret overtype mode

    text_dc                     _canvas;            // canvas dc
    HPEN                        _caret_pen;         // caret pen
    int                         _max_canvas_width;  // maximum canvas width
    POINT                       _view_pos;          // top-left point of view
    POINT                       _scroll_pos;        // current scroll position
    RECT                        _margin;            // display margin

    COLORREF                    _sel_text_color, _sel_bg_color;
    COLORREF                    _mark_text_color, _mark_bg_color;
    COLORREF                    _caret_text_color, _caret_bg_color;

    bool                        _canvas_dirty;      // dirty flag of text container
};
